//
//  SenseArMaterialRender.h
//  SenseArMaterial
//
//  Created by sluin on 16/10/8.
//  Copyright © 2016年 SenseTime. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>

#import "SenseArMaterial.h"
#import "SenseArMaterialPart.h"
#import "SenseArFrameActionInfo.h"
#import "SenseArCptMaterial.h"
#import "SenseArDetectResult.h"
#import "SenseArTimeReportData.h"
#import "SenseArHotLinkClickResult.h"

/**
 渲染素材回调
 */
typedef void(^SenseArRenderCallBack)(NSString *materialID);

/**
 检测、美颜、手势、前后背景时间回调
 
 @param data 回调数据
 */
typedef void(^SenseArTimeReportDataCallBack)(SenseArTimeReportData *data);

@protocol SenseArRenderDelegate <NSObject>

/*
 素材最小渲染时间完成回调
 */
- (void)onCptAdTimeCompleted:(NSString *)materailID;
/*
 素材被成功触发回调
 */
- (void)onCptADTriggersCompleted:(NSString *)materailID;
/*
 广告上报请求成功回调
 */
- (void)onCptReportSuccessResultWithMaterialID:(NSString *)materialID result:(SenseArCptConfirmResult)result description:(NSString *)desc;
/*
 广告上报请求失败回调
 */
- (void)onCptReportFailureResultWithMaterialID:(NSString *)materialID errorCode:(int)errorCode message:(NSString *)message;

@end

/*
 音频播放模块回调 , 如需要自行实现贴纸音频播放模块需遵循该协议并合理调用 audioPlayerDidFinishPlaying: 方法.
 */
@protocol SenseArAudioPlayerDelegate <NSObject>



/**
 音频播放模块将要加载音频数据

 @param soundData 音频数据
 @param strSoundName 音频名称
 */
- (void)audioPlayerWillLoadSoundData:(NSData *)soundData withName:(NSString *)strSoundName;


/**
 音频模块将要播放指定名称音频

 @param strSoundName 音频名称
 @param iLoop 音频需要播放的循环次数 , 0 代表无限循环
 */
- (void)audioPlayerWillPlaySound:(NSString *)strSoundName loop:(int)iLoop;


/**
 音频模块将要停止播放指定名称音频

 @param strSoundName 音频名称
 */
- (void)audioPlayerWillStopSound:(NSString *)strSoundName;

@end



FOUNDATION_EXTERN const uint32_t SENSEAR_ENABLE_HUMAN_ACTION;             ///<开启动作检测功能
FOUNDATION_EXTERN const uint32_t SENSEAR_ENABLE_BEAUTIFY;                 ///<开启美颜功能

// SenseAR Expression检测配置参数
FOUNDATION_EXTERN const uint32_t SENSEAR_EXPRESSION_DETECT_FACE;          ///< 检测脸部动作
FOUNDATION_EXTERN const uint32_t SENSEAR_EXPRESSION_DETECT_HAND;          ///< 检测手势



/**
 美颜参数类型
 */
typedef enum : NSUInteger {
    
    /**
     红润强度 [0 , 1.0] , 默认值0.36, 0.0不做红润
     */
    BEAUTIFY_REDDEN_STRENGTH = 0,
    
    /**
     磨皮强度 [0 , 1.0] , 默认值0.74, 0.0不做磨皮
     */
    BEAUTIFY_SMOOTH_STRENGTH,
    
    /**
     美白强度 [0 , 1.0] , 默认值0.02, 0.0不做美白
     */
    BEAUTIFY_WHITEN_STRENGTH,
    
    /**
     大眼比例 [0 , 1.0] , 默认值0.13, 0.0不做大眼效果
     */
    BEAUTIFY_ENLARGE_EYE_RATIO,
    
    /**
     瘦脸比例 [0 , 1.0] , 默认值0.11, 0.0不做瘦脸效果
     */
    BEAUTIFY_SHRINK_FACE_RATIO,
    
    /*
     小脸比例 [0 , 1.0] , 默认值0.10, 0.0不做小脸效果
     */
    BEAUTIFY_SHRINK_JAW_RATIO,
    
    /*
     饱和度强度, [0, 1.0], 默认值0.20, 0.0不做饱和度处理
     */
    BEAUTIFY_SATURATION_STRENGTH,
    
    /*
     对比度强度, [0, 1.0], 默认值0.05, 0.0不做对比度处理
     */
    BEAUTIFY_CONTRAST_STRENGTH
    
} BeautifyType;



/**
 客户端类型配置状态
 */
typedef enum : NSUInteger {
    
    /**
     成功
     */
    RENDER_SUCCESS = 0,
    
    /**
     素材文件不被当前版本支持或格式不正确
     */
    RENDER_UNSUPPORTED_MATERIAL,
    
    /**
     素材文件不存在
     */
    RENDER_MATERIAL_NOT_EXIST,
    
    /**
     底层渲染引擎出错 , 比如输入参数非法等
     */
    RENDER_ENGINE_ERROR,
    
    /**
     未授权
     */
    RENDER_NOT_AUTHORIZED,
    
    /**
     不可知状态
     */
    RENDER_UNKNOWN,
    
} SenseArRenderStatus;


/**
 图像旋转角度
 */
typedef enum : NSUInteger {
    
    /**
     图像不需要转向
     */
    CLOCKWISE_ROTATE_0 = 0,
    
    /**
     图像需要顺时针旋转90度
     */
    CLOCKWISE_ROTATE_90,
    
    /**
     图像需要顺时针旋转180度
     */
    CLOCKWISE_ROTATE_180,
    
    /**
     图像需要顺时针旋转270度
     */
    CLOCKWISE_ROTATE_270
    
} SenseArRotateType;


/**
 公开检测结果数据类型
 */
typedef enum : NSUInteger {
    
    /**
     人脸数据
     */
    SENSEAR_EXPOSED_DATA_FACE = 0,
    
    /**
     手势数据
     */
    SENSEAR_EXPOSED_DATA_HAND,
    
    /**
     前后背景数据
     */
    SENSEAR_EXPOSED_DATA_SEGMENT
    
} SenseArDetectDataType;


/**
 素材渲染模块
 */
@interface SenseArMaterialRender : NSObject


/**
 素材开始渲染
 */
@property (nonatomic, copy) SenseArRenderCallBack renderBegin;

/**
 素材结束渲染
 */
@property (nonatomic, copy) SenseArRenderCallBack renderEnd;


/*
 素材渲染代理
 */
@property (nonatomic , weak) id <SenseArRenderDelegate> delegate;



/*
 音频播放代理 , 仅当需要自己实现贴纸音效播放时设置 .
 */
@property (nonatomic , weak) id <SenseArAudioPlayerDelegate> audioPlayerDelegate;



/**
 时间数据
 */
@property (nonatomic, copy) SenseArTimeReportDataCallBack timeReportCallBack;

/**
 创建渲染模块
 
 @param strModelPath 模型路径
 @param iConfig      开启功能的配置
 @param context      渲染使用的 OpenGL 环境
 
 @return 渲染模块实例
 */
+ (SenseArMaterialRender *)instanceWithModelPath:(NSString *)strModelPath
                                          config:(uint32_t)iConfig
                                         context:(EAGLContext *)context;



/**
 初始化 OpenGL 资源
 */
- (void)initGLResource;



/**
 释放 OpenGL 资源
 */
- (void)releaseGLResource;


/**
 设置素材图像所占用的最大内存 (MB) , 默认 150 MB , 素材过大时 , 循环加载 , 降低内存; 贴纸较小时 , 全部加载 , 降低 CPU

 @param fSize 设置的内存大小
 @return 是否设置成功 成功 YES , 失败 NO
 */
- (BOOL)setMaxImageMemory:(float)fSize;


/**
 检查是否触发热链接点击 , 如果返回值不为空 , 则触发热链接点击 .

 @param clickPosition           点击视图的点
 @param imageSize               图像的尺寸
 @param previewSize             视图的尺寸
 @param previewOriginPosition   视图左上角相对于屏幕左上角的坐标
 @return 点击结果 如果触发热链接点击该值不为空 , 返回值可用于调用 reportAdClickedWithUserID 上报 .
 */
- (SenseArHotLinkClickResult *)checkHotLinkWithClickedPosition:(CGPoint)clickPosition
                                                     imageSize:(CGSize)imageSize
                                                   previewSize:(CGSize)previewSize
                                         previewOriginPosition:(CGPoint)previewOriginPosition;



/**
 点击热链接跳转上报

 @param strUserID strUserID 当前用户ID
 @param strBroadcasterID 主播 ID (可选), 如果当前用户为观众端 , 该参数代表所观看的主播 ID , 如果为其他 Client 类型没有可以传 nil .
 @param clickResult checkHotLinkWithClickedPosition 返回的点击结果
 */
- (void)reportAdClickedWithUserID:(NSString *)strUserID
                    broadcasterID:(NSString *)strBroadcasterID
                      clickResult:(SenseArHotLinkClickResult *)clickResult;




/**
 设置图像大小
 
 @param iWidth  图像宽度
 @param iHeight 图像高度
 @param iStride 图像跨度
 */
- (void)setFrameWidth:(int)iWidth height:(int)iHeight stride:(int)iStride;


/**
 设置美颜参数
 
 @param fValue        参数大小
 @param iBeautifyType 参数类型
 
 @return 是否设置成功
 */
- (BOOL)setBeautifyValue:(float)fValue forBeautifyType:(BeautifyType)iBeautifyType;


/**
 设置当前素材
 
 @param material 当前素材
 @return 返回状态
 */
- (SenseArRenderStatus)setMaterial:(SenseArMaterial *)material;


/**
 对图像进行美颜 , 获取图像绘制信息
 
 @param pFrameInfo      返回的图像绘制信息 , 需要用户分配内存 , 建议分配 10KB .
 @param pLength         返回的信息长度
 @param iPixelFormatIn  输入图像格式
 @param pImageIn        输入图像
 @param iTextureIn      输入 textureID
 @param iRotateType     图像需要旋转的角度
 @param bNeedsMirroring 图像是否需要镜像
 @param iPixelFormatOut 图像输出的格式
 @param pImageOut       输出图像
 @param iTextureOut     输出 textureID
 @param detectResult    检测结果
 @return 渲染模块的状态
 */
- (SenseArRenderStatus)beautifyAndGenerateFrameInfo:(Byte *)pFrameInfo
                                    frameInfoLength:(int *)pLength
                                  withPixelFormatIn:(SenseArPixelFormat)iPixelFormatIn
                                            imageIn:(Byte *)pImageIn
                                          textureIn:(GLuint)iTextureIn
                                         rotateType:(SenseArRotateType)iRotateType
                                     needsMirroring:(BOOL)bNeedsMirroring
                                     pixelFormatOut:(SenseArPixelFormat)iPixelFormatOut
                                           imageOut:(Byte *)pImageOut
                                         textureOut:(GLuint)iTextureOut
                                       detectResult:(SenseArDetectResult *)detectResult;


/**
 渲染 Material 效果并根据需求可以输出渲染后的图像
 
 @param pFrameInfo    渲染素材需要的绘制信息 (beautifyAndGenerateFrameInfo 结果)
 @param iTextureIdIn  输入 textureID
 @param iTextureIdOut 输出 textureID
 @param iPixelFormat  输出的图像格式
 @param pImageOut     输出的图像 , 传 NULL 表示不输出图像 , 性能会更好一些 .
 
 @return 渲染的状态
 */
- (SenseArRenderStatus)renderMaterialWithFrameInfo:(Byte *)pFrameInfo
                                   frameInfoLength:(int)iLength
                                         textureIn:(GLuint)iTextureIdIn
                                        textureOut:(GLuint)iTextureIdOut
                                       pixelFormat:(SenseArPixelFormat)iPixelFormat
                                          imageOut:(Byte *)pImageOut;



/**
 获取当前素材所有 part 信息
 
 @return 当前素材所有 part 信息 , 按照 zposition 排序
 */
- (NSArray <SenseArMaterialPart *>*)getMaterialParts;


/**
 设置每个 part 是否被渲染 , 顺序需要与 getMaterialParts: 获得的 parts 顺序一致 .
 
 @param arrMaterialParts part 序列
 */
- (void)enableMaterialParts:(NSArray <SenseArMaterialPart *>*)arrMaterialParts;


/**
 获取当前帧中的人脸、手势、背景的动作行为信息 , 需要在 beautifyAndGenerateFrameInfo: 之后调用,否则可能返回上一帧数据.
 
 @return 当前帧中的人脸、手势、背景的动作行为信息
 */
- (SenseArFrameActionInfo *)getCurrentFrameActionInfo;


/**
 强制要求引擎检测识别项 . 缺省下 SenseAr 会根据贴纸要求检测所需的人体特征和行为信息 .
 
 @param iDetectConfig 需要检测的人体行为 , 参考 SenseArMaterialPart , 例如 SENSEAR_FACE_DETECT | SENSEAR_EYE_BLINK | SENSEAR_MOUTH_AH | SENSEAR_HEAD_YAW
 @return 是否设置成功 . YES : 成功 , NO : 失败
 */
- (BOOL)forceDetectWithTypes:(uint64_t)iDetectConfig;


/**
 重置检测项 , SDK 会根据贴纸需求进行默认的检测
 */
- (void)resetDetectTypes;


/**
 检测出的结果数据是否公开 , 并且可以使用
 
 @param iDetectDataType 检测类型 , 例如 SENSEAR_EXPOSED_DATA_FACE
 @return 该类型数据是否公开 , 并且可以使用 . YES : 公开,可用 , NO : 未公开,不可用
 */
- (BOOL)isDetectDataOpenedWithType:(SenseArDetectDataType)iDetectDataType;

/**
 设置需要检测的动作，在beautyAndGenerateFrameInfo之前调用 , 目前最多支持 SENSEAR_EXPRESSION_COUNT 个
 
 @param arrActions 需要检测的动作数组. 可以配置为 @[@(SENSEAR_EXPRESSION_FACE_ALL) , @(SENSEAR_EXPRESSION_HAND_ALL)] , 会覆盖之前的配置. 当不需要使用SenseAR Expression功能时, 需要设置为 nil 以清空检测
 @return 是否设置成功
 */
- (BOOL)setExpressionDetectActions:(NSArray <NSNumber *> *)arrActions;


/**
 检测动作. 在beautifyAndGenerateFrameInfo函数之后调用
 
 @param arrActions 需要检测的动作
 @return 是否检测成功
 */
- (BOOL)checkExpressionResultActions:(NSArray <NSNumber *> *)arrActions;



/**
 告知 render 指定名称音频播放完毕 , 仅当实现 SenseArAudioPlayerDelegate 协议列举方法后 , 该方法才生效 .

 @param strSoundName 音频名称
 */
- (void)audioPlayerDidFinishPlaying:(NSString *)strSoundName;

@end
